/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <dlfcn.h>
#include <gnu/lib-names.h>
#include "dal/allocator.h"
#include "dal/config_loader.h"

#include "dal_cyclonedds/converter/StatusConverter.hpp"
#include "dal_cyclonedds/converter/StatusMaskConverter.hpp"
#include "dal_cyclonedds/converter/QosConverter.hpp"
#include "dal_cyclonedds/converter/RetTypeConverter.hpp"
#include "dal_cyclonedds/converter/GuidConverter.hpp"
#include "dal_cyclonedds/listener_impl/DomainParticipantListenerImpl.hpp"
#include "dal_cyclonedds/utils/DomainConfigMap.hpp"
#include "dal_cyclonedds/utils/TopicSertypeManager.hpp"
#include "dal_cyclonedds/utils/ParticipantMonitor.hpp"
#include "dal_cyclonedds/utils/log.hpp"
#include "dal_cyclonedds/utils/EntityGetter.hpp"

#include "dds/ddsrt/environ.h"
#include "dds/ddsrt/expand_vars.h"
#include "dds/ddsrt/heap.h"

dal_domain_participant_listener_t*
dal_create_domain_participant_listener(dal_domain_participant_listener_callbacks_t callbacks)
{
    dal_domain_participant_listener_t* listener = dal_domain_participant_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = (new DomainParticipantListenerImpl(callbacks));
    if (!listener->handle_) {
        LOG_ERROR() << "DomainParticipantListenerImpl memory allocation failed." << LOG_ENDL;
        dal_domain_participant_listener_free(listener);
        return nullptr;
    }

    return listener;
}

dal_ret_t dal_delete_domain_participant_listener(dal_domain_participant_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    DomainParticipantListenerImpl* listener_impl =
        reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    if (listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete domain participant listener because it's entity still exist."
                    << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete listener_impl;
    dal_domain_participant_listener_free(listener);
    reinterpret_cast<dal_domain_participant_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_domain_participant_t* dal_create_domain_participant(dal_domain_id domain_id,
                                                        dal_domain_participant_qos_t* qos,
                                                        dal_domain_participant_listener_t* listener,
                                                        dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (domain_id < 0 || domain_id > DAL_DOMAIN_ID_MAX) {
        LOG_ERROR_WITH_LOCATION()
            << "domain_id is invalid, a number between 0 and 160 is recommended" << LOG_ENDL;
        return nullptr;
    }

    dal_domain_participant_t* participant = dal_domain_participant_allocate();
    if (!participant) {
        LOG_ERROR_WITH_LOCATION() << "dal domain_participant allocate failed." << LOG_ENDL;
        return nullptr;
    }

    dds_entity_t domain_from_conf = -1;
    std::string multicast_conf = "";
    std::string unicast_conf = "";
    if (qos) {
        QosConverter::GetInstance()->SetMulticastToCycloneddsParticipantQos(qos, multicast_conf);
        QosConverter::GetInstance()->SetUnicastToCycloneddsParticipantQos(qos, unicast_conf);
    }
    try {
        domain_from_conf = DomainConfigMap::GetInstance()->GetDomainConfig(domain_id);
        if (domain_from_conf == -1) {
            if (!multicast_conf.empty() || !unicast_conf.empty()) {
                char* domain_config = NULL;
                domain_config =
                    ddsrt_expand_envvars((multicast_conf + unicast_conf).c_str(), domain_id);
                domain_from_conf = dds_create_domain(domain_id, domain_config);
                ddsrt_free(domain_config);
            } else {
                const char* domain_config = NULL;
                ddsrt_getenv("CYCLONEDDS_URI", &domain_config);
                domain_from_conf = dds_create_domain(domain_id, domain_config);
            }
        }
        DomainConfigMap::GetInstance()->AddDomainConfig(domain_id, domain_from_conf);
    }
    catch (...) {
        LOG_ERROR_WITH_LOCATION() << "load domain configuration xml string failed.";
    }

    dds_qos_t* c_qos = dds_create_qos();
    d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToCyclonedds(qos, c_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        dds_delete_qos(c_qos);
        return nullptr;
    }

    DomainParticipantListenerImpl* c_listener_impl =
        listener ? reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_) : nullptr;

    dds_listener_t* c_listener = NULL;
    if (c_listener_impl != nullptr) {
        c_listener = c_listener_impl->CycloneDDSListener();
    }

    dal_participant_monitor_callback monitor_callback = nullptr;
    if (c_listener_impl != nullptr) {
        monitor_callback = c_listener_impl->get_participant_monitor_callback();
    }

    participant_born_info_s participant_born_info;
    participant_born_info.born_time_ = dds_time();
    if (monitor_callback != nullptr) {
        participant_born_info.monitor_flag_ = 1;
    }
    dds_qset_userdata(c_qos, &participant_born_info, sizeof(participant_born_info_s));

    dds_entity_t c_participant = -1;
    // TODO: if listener of participant-monitor's reader is enabled, outer participant's listener
    // need be NULL. issue for CycloneDDS :
    // [https://github.com/eclipse-cyclonedds/cyclonedds/issues/1706]
    c_participant = dds_create_participant(domain_id, c_qos, c_listener);
    dds_delete_qos(c_qos);

    if (c_participant < 0) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant failed." << LOG_ENDL;
        if (domain_from_conf > 0) {
            DomainConfigMap::GetInstance()->DeleteDomainConfig(domain_id);
            domain_from_conf = -1;
        }
        dal_domain_participant_free(participant);
        return nullptr;
    }

    DalDomainParticipantImpl* participant_impl =
        new DalDomainParticipantImpl(c_participant, c_listener_impl);
    if (domain_from_conf > 0) {
        participant_impl->SetDomainFromConf(domain_from_conf);
    }
    participant->handle_ = participant_impl;

    if (monitor_callback != nullptr) {
        dal_guid_t d_guid = { };
        d_guid = dal_domain_participant_get_guid(participant);

        ParticipantMonitor& pm = ParticipantMonitor::GetInstance();
        void* mc_data = c_listener_impl->get_participant_monitor_callback_data();
        pm.SetMonitor(c_participant, monitor_callback, mc_data);
    }
    return participant;
}

dal_domain_participant_t*
dal_create_domain_participant_with_profile(dal_domain_id domain_id,
                                           dal_domain_participant_qos_t* qos,
                                           const char* profile_name,
                                           dal_domain_participant_listener_t* listener,
                                           dal_status_mask_t mask)
{
    return dal_create_domain_participant(domain_id, qos, listener, mask);
}
dal_ret_t dal_delete_domain_participant(dal_domain_participant_t* participant)
{
    dds_return_t c_ret;
    dds_entity_t c_participant = get_cyclone_domain_participant(participant);
    if (c_participant <= 0) {
        LOG_ERROR_WITH_LOCATION() << "participant invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dds_domainid_t domain_id;
    c_ret = dds_get_domainid(c_participant, &domain_id);
    if (DDS_RETCODE_OK != c_ret) {
        LOG_ERROR_WITH_LOCATION() << "delete domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }

    if (!TopicSertypeManager::GetInstance()->IsParticipantNoEntity(c_participant)) {
        LOG_ERROR_WITH_LOCATION()
            << "delete domain participant failed, because it still has entities." << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    c_ret = dds_delete(c_participant);
    dal_ret_t d_ret = RetTypeConverter::GetInstance()->ConvertRetFromCycloneddsToDal(c_ret);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "delete domain participant failed." << LOG_ENDL;
        return d_ret;
    }

    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "delete domain from config failed." << LOG_ENDL;
        return d_ret;
    }

    bool ret = DomainConfigMap::GetInstance()->DeleteDomainConfig(domain_id);
    if (!ret) {
        LOG_ERROR_WITH_LOCATION() << "delete domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }

    DalDomainParticipantImpl* participant_impl =
        reinterpret_cast<DalDomainParticipantImpl*>(participant->handle_);
    delete participant_impl;
    dal_domain_participant_free(participant);
    reinterpret_cast<dal_domain_participant_t*>(participant)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_guid_t dal_domain_participant_get_guid(dal_domain_participant_t* participant)
{
    dal_guid_t d_guid = { };
    dds_entity_t c_participant = (reinterpret_cast<DalDomainParticipantImpl*>(participant->handle_))
                                     ->CycloneddsDomainParticipant();
    dds_guid_t c_guid;
    if (dds_get_guid(c_participant, &c_guid) != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "get domain participant guid failed." << LOG_ENDL;
        return d_guid;
    }
    ddsi_guid_t c_guid_s;
    memcpy(&c_guid_s, &c_guid, sizeof(ddsi_guid_t));
    GuidConverter::GetInstance()->ConvertGuidFromCycloneddsToDal(&c_guid_s, &d_guid);
    return d_guid;
}

dal_ret_t dal_domain_participant_listener_set_participant_monitor_callback(
    dal_domain_participant_listener_t* listener,
    dal_participant_monitor_callback callback,
    void* callback_data)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }
    auto listener_impl = reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    listener_impl->set_participant_monitor_callback(callback);
    listener_impl->set_participant_monitor_callback_data(callback_data);
    return DAL_RETCODE_OK;
}

dal_ret_t dal_domain_participant_delete_contained_entities(dal_domain_participant_t* participant)
{
    return DAL_RETCODE_OK;
}
